import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

/**
 *
 * @author 11140721050130
 * @date 16-3-24
 */
public class MybatisGenerator {

    private static Map<String, String> javaSqlTypeTransferMap;
    private static Properties prop;
    private static String folder;
    private static String DAO_TEMPLATE = "templates/daoTemplate.ftl";
    private static String CONFIG_TEMPLATE = "templates/configTemplate.ftl";
    private static String MODEL_TEMPLATE = "templates/modelTemplate.ftl";
    private static String MAPPER_TEMPLATE = "templates/mapperTemplate.ftl";
    private final String model_name;
    private final String model_param;
    private final String basePackage;
    private final String decollator = "_";

    public MybatisGenerator() {
        this.loadProp();
        model_name = prop.getProperty("modelName");
        model_param = loadModelParam();
        basePackage = prop.getProperty("package");
        File file = new File(this.getClass().getResource("/").getPath());
        folder = file.getParentFile().getParentFile() + "\\src\\main\\";


    }

    private String loadModelParam() {
        char[] chars = model_name.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void loadProp() {
        prop = new Properties();
        InputStream stream = null;
        try {
            stream = this.getClass().getResource("generator.properties").openStream();
            prop.load(stream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static {
        javaSqlTypeTransferMap = new HashMap<String, String>();
        javaSqlTypeTransferMap.put("int", "Integer");
        javaSqlTypeTransferMap.put("varchar", "String");
        javaSqlTypeTransferMap.put("char", "String");
        javaSqlTypeTransferMap.put("decimal", "java.math.BigDecimal");
        javaSqlTypeTransferMap.put("datetime", "java.util.Date");
        javaSqlTypeTransferMap.put("timestamp", "java.util.Date");
        javaSqlTypeTransferMap.put("text", "String");
        javaSqlTypeTransferMap.put("tinyint", "Integer");
        javaSqlTypeTransferMap.put("longtext", "String");
        javaSqlTypeTransferMap.put("bigint", "Long");
        javaSqlTypeTransferMap.put("mediumtext", "String");
        javaSqlTypeTransferMap.put("smallint", "Integer");
        javaSqlTypeTransferMap.put("bit", "Boolean");

    }

    public void mapper() {
        this.delGeneratedFiels();
        String url = prop.getProperty("url");
        String db = prop.getProperty("db");
        String user = prop.getProperty("user");
        String pass = prop.getProperty("pass");
        String basePackage = prop.getProperty("package");
        String entityPackage = basePackage + ".model";
        String daoPackage = basePackage + ".dao";
        String dbClass = prop.getProperty("dbClass");

        Connection con = null;
        try {
            Class.forName(dbClass);
            con = DriverManager.getConnection(url + db, user, pass);
            Statement st = con.createStatement();
            ResultSet dbNameRs = st
                    .executeQuery("SELECT distinct c.table_name FROM INFORMATION_SCHEMA.COLUMNS c where c.table_schema = '"
                            + db + "' ");
            List<String> includeTables = this.getIncludeTables(prop
                    .getProperty("tables"));
            while (dbNameRs.next()) {
                boolean createMapperTable = (includeTables != null && includeTables.contains(dbNameRs
                                                                                                     .getString(1)))
                        || includeTables == null
                        || "".equals(includeTables);
                if (createMapperTable) {
                    this.mapperTable(db, dbNameRs.getString(1), con, daoPackage,
                            daoPackage, entityPackage);
                }
            }

            dbNameRs.close();
            st.close();
            con.close();
        } catch (Exception e) {
            try {
                if (con != null){
                    con.close();
                }
            } catch (SQLException e1) {
            }
            e.printStackTrace();
        }
    }

    private List<String> getIncludeTables(String property) {
        List<String> ret = null;
        if (property != null && property.length() > 0) {
            ret = Arrays.asList(property.split(","));
        }
        return ret;
    }

    private void delGeneratedFiels() {
        File file = new File(folder);
        if (file.exists()) {
            File[] subs = file.listFiles();
            if (subs != null && subs.length > 0) {
                for (File sub : subs) {
                    sub.delete();
                }
            }
        }
        file.mkdirs();
    }

    private void mapperTable(String db, String tableName, Connection con,
                             String namespacePac, String daoPackage, String entityPac) {
        try {
            Statement st = con.createStatement();
            ResultSet rs = st
                    .executeQuery("SELECT DISTINCT c.column_name, c.DATA_TYPE, c.column_comment,c.IS_NULLABLE,c.CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS c WHERE c.table_name = '"
                            + tableName + "'");
            System.out.println("========================" + tableName
                    + "===============================");
            List<Column> columns = new ArrayList<Column>();
            while (rs.next()) {
                columns.add(this.getColumn(rs.getString(1), rs.getString(2),
                        rs.getString(3), rs.getString(4), rs.getLong(5)));
            }
            ResultSet keyRs = st
                    .executeQuery("SELECT k.column_name FROM information_schema.table_constraints t "
                            + " JOIN information_schema.key_column_usage k "
                            + " USING(constraint_name,table_schema,table_name) "
                            + " WHERE t.constraint_type='PRIMARY KEY' "
                            + " AND t.table_schema='"
                            + db
                            + "'  AND t.table_name='" + tableName + "'");
            String keyColumn = null;
            if (keyRs.next()) {
                keyColumn = keyRs.getString(1).toLowerCase();
            }

            ResultSet tableCommentRs = st
                    .executeQuery("SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='"
                            + db + "' AND table_name='" + tableName + "'");
            String tableComment = null;
            if (tableCommentRs.next()) {
                tableComment = tableCommentRs.getString(1);
            }

            Table table = this.createTable(namespacePac, entityPac, tableName,
                    columns, keyColumn, tableComment,
                    daoPackage);

            this.createMapperFile(table);
            this.createMybatis(table);
            this.createDaoFile(table);
            this.createModelFile(table);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Table createTable(String namespacePac, String entityPac,
                              String tableName, List<Column> columns, String keyColumn,
                              String tableComment,
                              String daoPackage) {
        Table table = new Table();
        table.setColumns(columns);
        if (keyColumn != null) {
            table.setKeyColumn(keyColumn);
        }
        table.setNamespacePackage(namespacePac);
        table.setNameSpace(model_name + "Model");
        table.setEntityParam(model_param);
        table.setDaoName(model_name
                + "Dao");
        table.setResultMapId(model_name
                + "Map");
        table.setEntityPackage(entityPac);
        table.setEntityName(model_name + "Model");

        table.setTableName(tableName);
        table.setTableComment(tableComment);
        table.setDaoPackage(daoPackage);
        return table;
    }

    private void createModelFile(Table table) {
        try {
            Template template = this.getTemplateCfg().getTemplate(
                    MODEL_TEMPLATE);
            File file = new File(folder + "java\\" + basePackage.replace(".", "\\") + "\\" + "model\\");
            if (!file.exists()) {
                file.mkdirs();
            }
            File javaFile = new File(file.getPath() + "\\" + table.getEntityName() + ".java");
            Writer out = new FileWriter(javaFile);
            template.process(table, out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createMybatis(Table table) {
        try {
            String templateFileName = "templates/configTemplate.ftl";
            Template template = this.getTemplateCfg().getTemplate(
                    templateFileName);
            File file = new File(folder + "resources\\" + "config\\");
            if (!file.exists()) {
                file.mkdirs();
            }
            File xmlFile = new File(file.getPath() + "\\" + "mybatis.xml");
            Writer out = new FileWriter(xmlFile);
            template.process(table, out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createDaoFile(Table table) {
        try {
            Template template = this.getTemplateCfg().getTemplate(
                    DAO_TEMPLATE);
            File file = new File(folder + "java\\" + basePackage.replace(".", "\\") + "\\" + "dao\\");
            if (!file.exists()) {
                file.mkdirs();
            }
            File javaFile = new File(file.getPath() + "\\" + table.getDaoName() + ".java");
            Writer out = new FileWriter(javaFile);
            template.process(table, out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void createMapperFile(Table table) {
        try {
            Template template = this.getTemplateCfg().getTemplate(
                    MAPPER_TEMPLATE);
            File file = new File(folder + "resources\\" + "mapper\\");
            if (!file.exists()) {
                file.mkdirs();
            }
            File mapperFile = new File(file.getPath() + "\\" + table.getEntityParam()
                    + "Mapper.xml");
            Writer out = new FileWriter(mapperFile);
            template.process(table, out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Column getColumn(String dbColumn, String colType,
                             String colComment, String isNullable, Long characterMaximumLength) {
        Column col = new Column();
        col.setDbColumn(this.getDbColumn(dbColumn));
        col.setField(this.getBeanFieldFromDbColumn(dbColumn));
        if (javaSqlTypeTransferMap.get(colType) == null
                || "".equals(javaSqlTypeTransferMap.get(colType))) {
            System.out.println("colType=----------------------------" + colType
                    + "--------------" + dbColumn);
        }
        col.setColJavaType(javaSqlTypeTransferMap.get(colType));
        col.setFildType(javaSqlTypeTransferMap.get(colType));
        col.setDbType(colType);
        col.setDbComment(colComment);
        col.setIsNullable(isNullable);
        col.setCharacterMaximumLength(characterMaximumLength);
        return col;
    }


    private String getBeanFieldFromDbColumn(String dbColumn) {
        System.out.println(dbColumn);
        String ret;
        if (dbColumn.contains(decollator)) {
            String[] pieces = dbColumn.toLowerCase().split("_");
            StringBuffer sb = new StringBuffer(pieces[0]);
            if (pieces.length > 1) {
                for (int i = 1; i < pieces.length; i++) {
                    sb.append(String.valueOf(pieces[i].charAt(0)).toUpperCase())
                            .append(pieces[i].substring(1));
                }
            }
            ret = sb.toString();
        } else {
            ret = dbColumn;
        }
        return ret;
    }

    private String getBeanNameFromTalbeName(String tableName) {
        String[] pieces = tableName.split("_");
        StringBuffer sb = new StringBuffer();
        for (int i = 1; i < pieces.length - 1; i++) {
            sb.append(String.valueOf(pieces[i].charAt(0)).toUpperCase())
                    .append(pieces[i].substring(1));
        }
        return sb.toString();
    }

    private String getDbColumn(String dbColumn) {
        String ret;
        if (dbColumn.contains(decollator)) {
            ret = dbColumn.toLowerCase();
        } else {
            ret = dbColumn;
        }
        return ret;
    }

    private Configuration getTemplateCfg() {
        try {
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
            cfg.setClassForTemplateLoading(MybatisGenerator.class, "/");
            cfg.setDefaultEncoding("UTF-8");
            cfg.setOutputEncoding("UTF-8");
            cfg.setLocale(Locale.US);
            return cfg;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
